using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using WiMo.Games.Drawables;

namespace WiMo.Games.DrawingXNA.Drawables
{
    public class WiMoTexture
    {
        public static void DrawShape(SpriteBatch spriteBatch, ISpriteShape shape)
        {
            switch (shape.ShapeType)
            {
                case ShapeType.LineList:
                    if (shape.DrawInformation == null)
                        shape.DrawInformation = CreateLineListTexture(spriteBatch, shape);
                    break;

                case ShapeType.Rectangle:
                    if (shape.DrawInformation == null)
                        shape.DrawInformation = CreateRectangleTexture(spriteBatch, shape);

                    break;

                case ShapeType.Ellipse:
                    if (shape.DrawInformation == null)
                        shape.DrawInformation = CreateEllipseTexture(spriteBatch, shape);
                    break;

                case ShapeType.Polygon:
                    if (shape.DrawInformation == null)
                        shape.DrawInformation = CreateLineListTexture(spriteBatch, shape);
                    break;
            }

            var texture2D = shape.DrawInformation as Texture2D;

            if (texture2D != null)
            {
                switch (shape.Origin)
                {
                    case Origin.TopLeft:
                        spriteBatch.Draw(texture2D, shape.Position.GetXNAVector2(), Microsoft.Xna.Framework.Graphics.Color.White);
                        break;
                    case Origin.Center:
                        spriteBatch.Draw(texture2D, (shape.Position - (shape.Size / 2)).GetXNAVector2(), Microsoft.Xna.Framework.Graphics.Color.White);
                        break;
                }
            }
        }

        private static Texture2D CreateRectangleTexture(SpriteBatch spriteBatch, ISpriteShape shape)
        {
            if (shape.Size.Width == 0 || shape.Size.Height == 0)
                return null;

            var texture = new Texture2D(spriteBatch.GraphicsDevice, shape.Size.Width, shape.Size.Height);

            var colors = new Microsoft.Xna.Framework.Graphics.Color[shape.Size.Height * shape.Size.Width];

            for (var i = 0; i < colors.Length; ++i)
                colors[i] = shape.FillColor.GetXNAColor();

            for (var x = 0; x < shape.Size.Width; ++x)
            {
                for (var y = 0; y < shape.BorderWidth; ++y)
                {
                    colors[x + y * shape.Size.Width] = shape.BorderColor.GetXNAColor();
                    colors[x + ((shape.Size.Height - 2) - y) * shape.Size.Width] = shape.BorderColor.GetXNAColor();
                }
            }

            for (var y = 1; y < shape.Size.Height - 1; ++y)
            {
                for (var x = 0; x < shape.BorderWidth; ++x)
                {
                    colors[x + y * shape.Size.Width] = shape.BorderColor.GetXNAColor();
                    colors[((shape.Size.Width - 1) - x) + y * shape.Size.Width] = shape.BorderColor.GetXNAColor();
                }
            }

            texture.SetData<Microsoft.Xna.Framework.Graphics.Color>(colors);

            return texture;
        }

        private static void Swap<T>(ref T val1, ref T val2)
        {
            var temp = val1;
            val1 = val2;
            val2 = temp;
        }
        

        private static Texture2D CreateLineListTexture(SpriteBatch spriteBatch, ISpriteShape shape)
        {
            var bitmap = new WiMoBitmap(shape.Size);

            for (var idx = 1; idx < shape.Points.Count; ++idx)
                RenderLine(bitmap, shape.BorderColor, shape.Points[idx - 1], shape.Points[idx]);            

            if(shape.FillColor != Color.Transparent || shape.Texture != null)
                bitmap.FillPolygon(shape);

            return bitmap.GetTexture(spriteBatch.GraphicsDevice);
        }


        public static double FPart(double x)
        {
            return x - Math.Round(x);
        }

        public static double RFPart(double x)
        {
            return 1.0 - FPart(x);
        }

        private static void RenderLine(IWiMoBitmap bitmap, Color color, double x1, double x2, double y1, double y2, double dx, double dy, bool steep)
        {
            if (x2 < x1)
            {
                Swap(ref x1, ref x2);
                Swap(ref y1, ref y2);
            }

            var gradient = dy / dx;
            var xend = Math.Round(x1);
            var yend = y1 + gradient * (xend - x1);

            var xgap = RFPart(x1 + 0.5);
            var xpxl1 = xend;
            var ypxl1 = Math.Floor(yend);

            if (steep)
            {
                bitmap.SetPixel(ypxl1, xpxl1, color, RFPart(yend) * xgap);
                bitmap.SetPixel(ypxl1 + 1, xpxl1, color, FPart(yend) * xgap);
            }
            else
            {
                bitmap.SetPixel(xpxl1, ypxl1, color, RFPart(yend) * xgap);
                bitmap.SetPixel(xpxl1, ypxl1 + 1, color, FPart(yend) * xgap);
            }

            var intery = yend + gradient;

            xend = Math.Round(x2);
            yend = y2 + gradient * (xend - x2);

            xgap = FPart(x2 + 0.5f);

            var xpxl2 = xend;
            var ypxl2 = Math.Floor(yend);

            if (steep)
            {
                bitmap.SetPixel(ypxl2, xpxl2, color, RFPart(yend) * xgap);
                bitmap.SetPixel(ypxl2 + 1, xpxl2, color, FPart(yend) * xgap);
            }
            else
            {
                bitmap.SetPixel(xpxl2, ypxl2, color, RFPart(yend) * xgap);
                bitmap.SetPixel(xpxl2, ypxl2 + 1, color, FPart(yend) * xgap);                
            }

            for (var x = xpxl1 + 1; x < xpxl2; ++x)
            {
                if (steep)
                {
                    bitmap.SetPixel(Math.Floor(intery), x, color, RFPart(intery));
                    bitmap.SetPixel(Math.Floor(intery) + 1, x, color, FPart(intery));
                }
                else
                {
                    bitmap.SetPixel(x, Math.Floor(intery), color, RFPart(intery));
                    bitmap.SetPixel(x, Math.Floor(intery) + 1, color, FPart(intery));
                }
                intery += gradient;
            }
        }

        public static void RenderLine(IWiMoBitmap bitmap, Color color, Location start, Location end)
        {
            var x1 = (double)start.X;
            var x2 = (double)end.X;

            var y1 = (double)start.Y;
            var y2 = (double)end.Y;

            var dx = x2 - x1;
            var dy = y2 - y1;

            bool steep = Math.Abs(dx) < Math.Abs(dy);
            if (steep)
                RenderLine(bitmap, color, y1, y2, x1, x2, dy, dx, steep);
            else
                RenderLine(bitmap, color, x1, x2, y1, y2, dx, dy, steep);

        }
      

        enum fillingStates
        {
            Outside,
            OnEdge,
            Filling,
            Leaving
        }

        private static void FillTexture(ISpriteShape shape, Microsoft.Xna.Framework.Graphics.Color[] Colors)
        {
            fillingStates fillingState = fillingStates.Outside;

            for (var y = 0; y < shape.Size.Height; ++y)
            {
                int startFillIndex = 0;
                for (var x = 0; x < shape.Size.Width; ++x)
                {
                    int pixelIndex = x + (y * shape.Size.Width);
                    var baseColor = Colors[pixelIndex];
                    switch (fillingState)
                    {
                        case fillingStates.Outside:
                            if (baseColor == shape.BorderColor.GetXNAColor())
                                fillingState = fillingStates.OnEdge;

                            break;

                        case fillingStates.OnEdge:
                            if (baseColor != shape.BorderColor.GetXNAColor())
                            {
                                fillingState = fillingStates.Filling;
                                startFillIndex = pixelIndex;
                            }
                            break;
                        case fillingStates.Filling:
                            if (baseColor == shape.BorderColor.GetXNAColor())
                            {
                                fillingState = fillingStates.Leaving;
                                for (var fillIndex = startFillIndex; fillIndex < pixelIndex; ++fillIndex)
                                    Colors[fillIndex] = shape.FillColor.GetXNAColor();
                            }

                            break;
                        case fillingStates.Leaving:
                            if (baseColor != shape.BorderColor.GetXNAColor())
                                fillingState = fillingStates.Outside;
                            break;
                    }
                }

                fillingState = fillingStates.Outside;
            }
        }

        private static int GetColorIndex(int x, int y, int rowWidth)
        {
            return y * (rowWidth) + x;
        }

        public static void DrawEllipse(Microsoft.Xna.Framework.Graphics.Color[] colors, Microsoft.Xna.Framework.Graphics.Color borderColor, int xc, int yc, int r1, int r2)
        {
            var x = 0;
            var y = r2;

            var a2 = r1 * r1;
            var b2 = r2 * r2;

            var S = a2 * (1 - 2 * r2) + 2 * b2;
            var T = b2 - 2 * a2 * (2 * r2 - 1);

            var shapeWidth = xc * 2;

            colors[GetColorIndex(xc - x, yc - y, shapeWidth)] = borderColor;
            colors[GetColorIndex(xc + x, yc + y, shapeWidth)] = borderColor;
            colors[GetColorIndex(xc - x, yc + y, shapeWidth)] = borderColor;
            colors[GetColorIndex(xc + x, yc - y, shapeWidth)] = borderColor;

            do
            {
                if (S < 0)
                {
                    S += 2 * b2 * (2 * x + 3);
                    T += 4 * b2 * (x + 1);
                    x++;
                }
                else if (T < 0)
                {
                    S += 2 * b2 * (2 * x + 3) - 4 * a2 * (y - 1);
                    T += 4 * b2 * (x + 1) - 2 * a2 * (2 * y - 3);
                    x++;
                    y--;
                }
                else
                {
                    S -= 4 * a2 * (y - 1);
                    T -= 2 * a2 * (2 * y - 3);
                    y--;
                }

                colors[GetColorIndex(xc - x, yc - y, shapeWidth)] = borderColor;
                colors[GetColorIndex(xc + x, yc + y, shapeWidth)] = borderColor;
                colors[GetColorIndex(xc - x, yc + y, shapeWidth)] = borderColor;
                colors[GetColorIndex(xc + x, yc - y, shapeWidth)] = borderColor;
            }
            while (y > 0);

        }

        public static Texture2D CreateEllipseTexture(SpriteBatch spriteBatch, ISpriteShape shape)
        {
            var aCircle = new Texture2D(spriteBatch.GraphicsDevice, shape.Size.Width, shape.Size.Height, 1, TextureUsage.None, SurfaceFormat.Color);
            var colors = new Microsoft.Xna.Framework.Graphics.Color[shape.Size.Width * shape.Size.Height];

            var xc = shape.Size.Width / 2;
            var yc = shape.Size.Height / 2;

            var r1 = (shape.Size.Width / 2) - 1;
            var r2 = (shape.Size.Height / 2) - 1;

            for (var borderWidth = 0; borderWidth < shape.BorderWidth; ++borderWidth)
                DrawEllipse(colors, shape.BorderColor.GetXNAColor(), xc, yc, r1 - borderWidth, r2 - borderWidth);

            FillTexture(shape, colors);

            aCircle.SetData<Microsoft.Xna.Framework.Graphics.Color>(colors);

            return aCircle;
        }     
    }
}